/*
 * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
 * Copyright (c) 2013 China Beijing Armink <armink.ztl@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * File: $Id: mbrtu_m.c,v 1.60 2013/08/17 11:42:56 Armink Add Master Functions $
 */

/* ----------------------- System includes ----------------------------------*/
#include "stdlib.h"
#include "string.h"

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mb_m.h"
#include "mbrtu.h"
#include "mbframe.h"

#include "mbcrc.h"
#include "mbport.h"

#if MB_MASTER_RTU_ENABLED > 0
/* ----------------------- Defines ------------------------------------------*/
#define MB_SER_PDU_SIZE_MIN     4       /*!< Minimum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_MAX     256     /*!< Maximum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_CRC     2       /*!< Size of CRC field in PDU. */
#define MB_SER_PDU_ADDR_OFF     0       /*!< Offset of slave address in Ser-PDU. */
#define MB_SER_PDU_PDU_OFF      1       /*!< Offset of Modbus-PDU in Ser-PDU. */

/* ----------------------- Type definitions ---------------------------------*/
typedef enum {
	STATE_M_RX_INIT,              /*!< Receiver is in initial state. */
	STATE_M_RX_IDLE,              /*!< Receiver is in idle state. */
	STATE_M_RX_RCV,               /*!< Frame is beeing received. */
	STATE_M_RX_ERROR,              /*!< If the frame is invalid. */
} eMBMasterRcvState;

typedef enum {
	STATE_M_TX_IDLE,              /*!< Transmitter is in idle state. */
	STATE_M_TX_XMIT,              /*!< Transmitter is in transfer state. */
	STATE_M_TX_XFWR,              /*!< Transmitter is in transfer finish and wait receive state. */
} eMBMasterSndState;

/* ----------------------- Static variables ---------------------------------*/
static volatile eMBMasterSndState eSndState;
static volatile eMBMasterRcvState eRcvState;

static volatile UCHAR  ucMasterRTUSndBuf[MB_PDU_SIZE_MAX];
static volatile UCHAR  ucMasterRTURcvBuf[MB_SER_PDU_SIZE_MAX];
static volatile USHORT usMasterSendPDULength;

static volatile UCHAR* pucMasterSndBufferCur;
static volatile USHORT usMasterSndBufferCount;

static volatile USHORT usMasterRcvBufferPos;
static volatile BOOL   xFrameIsBroadcast = FALSE;

static volatile eMBMasterTimerMode eMasterCurTimerMode;

/* ----------------------- Start implementation -----------------------------*/
eMBErrorCode
eMBMasterRTUInit(UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity)
{
	eMBErrorCode    eStatus = MB_ENOERR;
	ULONG           usTimerT35_50us;

	ENTER_CRITICAL_SECTION();

	/* Modbus RTU uses 8 Databits. */
	if(xMBMasterPortSerialInit(ucPort, ulBaudRate, 8, eParity) != TRUE) {
		eStatus = MB_EPORTERR;
	} else {
		/* If baudrate > 19200 then we should use the fixed timer values
		 * t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
		 */
		if(ulBaudRate > 19200) {
			usTimerT35_50us = 35;       /* 1800us. */
		} else {
			/* The timer reload value for a character is given by:
			 *
			 * ChTimeValue = Ticks_per_1s / ( Baudrate / 11 )
			 *             = 11 * Ticks_per_1s / Baudrate
			 *             = 220000 / Baudrate
			 * The reload for t3.5 is 1.5 times this value and similary
			 * for t3.5.
			 */
			usTimerT35_50us = (7UL * 220000UL) / (2UL * ulBaudRate);
		}

		if(xMBMasterPortTimersInit((USHORT) usTimerT35_50us) != TRUE) {
			eStatus = MB_EPORTERR;
		}
	}

	EXIT_CRITICAL_SECTION();

	return eStatus;
}

void
eMBMasterRTUStart(void)
{
	ENTER_CRITICAL_SECTION();
	/* Initially the receiver is in the state STATE_M_RX_INIT. we start
	 * the timer and if no character is received within t3.5 we change
	 * to STATE_M_RX_IDLE. This makes sure that we delay startup of the
	 * modbus protocol stack until the bus is free.
	 */
	eRcvState = STATE_M_RX_INIT;
	vMBMasterPortSerialEnable(TRUE, FALSE);
	vMBMasterPortTimersT35Enable();

	EXIT_CRITICAL_SECTION();
}

void
eMBMasterRTUStop(void)
{
	ENTER_CRITICAL_SECTION();
	vMBMasterPortSerialEnable(FALSE, FALSE);
	vMBMasterPortTimersDisable();
	EXIT_CRITICAL_SECTION();
}

eMBErrorCode
eMBMasterRTUReceive(UCHAR* pucRcvAddress, UCHAR** pucFrame, USHORT* pusLength)
{
	eMBErrorCode    eStatus = MB_ENOERR;

	ENTER_CRITICAL_SECTION();
	assert_param(usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX);

	/* Length and CRC check */
	if((usMasterRcvBufferPos >= MB_SER_PDU_SIZE_MIN)
	        && (usMBCRC16((UCHAR*) ucMasterRTURcvBuf, usMasterRcvBufferPos) == 0)) {
		/* Save the address field. All frames are passed to the upper layed
		 * and the decision if a frame is used is done there.
		 */
		*pucRcvAddress = ucMasterRTURcvBuf[MB_SER_PDU_ADDR_OFF];

		/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
		 * size of address field and CRC checksum.
		 */
		*pusLength = (USHORT)(usMasterRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC);

		/* Return the start of the Modbus PDU to the caller. */
		*pucFrame = (UCHAR*) & ucMasterRTURcvBuf[MB_SER_PDU_PDU_OFF];
	} else {
		eStatus = MB_EIO;
	}

	EXIT_CRITICAL_SECTION();
	return eStatus;
}

eMBErrorCode
eMBMasterRTUSend(UCHAR ucSlaveAddress, const UCHAR* pucFrame, USHORT usLength)
{
	eMBErrorCode    eStatus = MB_ENOERR;
	USHORT          usCRC16;

	if(ucSlaveAddress > MB_MASTER_TOTAL_SLAVE_NUM) return MB_EINVAL;

	ENTER_CRITICAL_SECTION();

	/* Check if the receiver is still in idle state. If not we where to
	 * slow with processing the received frame and the master sent another
	 * frame on the network. We have to abort sending the frame.
	 */
	if(eRcvState == STATE_M_RX_IDLE) {
		/* First byte before the Modbus-PDU is the slave address. */
		pucMasterSndBufferCur = (UCHAR*) pucFrame - 1;
		usMasterSndBufferCount = 1;

		/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
		pucMasterSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
		usMasterSndBufferCount += usLength;

		/* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
		usCRC16 = usMBCRC16((UCHAR*) pucMasterSndBufferCur, usMasterSndBufferCount);
		ucMasterRTUSndBuf[usMasterSndBufferCount++] = (UCHAR)(usCRC16 & 0xFF);
		ucMasterRTUSndBuf[usMasterSndBufferCount++] = (UCHAR)(usCRC16 >> 8);

		/* Activate the transmitter. */
		eSndState = STATE_M_TX_XMIT;
		vMBMasterPortSerialEnable(FALSE, TRUE);
	} else {
		eStatus = MB_EIO;
	}

	EXIT_CRITICAL_SECTION();
	return eStatus;
}

BOOL
xMBMasterRTUReceiveFSM(void)
{
	BOOL            xTaskNeedSwitch = FALSE;
	UCHAR           ucByte;

	assert_param((eSndState == STATE_M_TX_IDLE) || (eSndState == STATE_M_TX_XFWR));

	/* Always read the character. */
	(void)xMBMasterPortSerialGetByte((CHAR*) & ucByte);

	switch(eRcvState) {
		/* If we have received a character in the init state we have to
		 * wait until the frame is finished.
		 */
		case STATE_M_RX_INIT:
			vMBMasterPortTimersT35Enable();
			break;

		/* In the error state we wait until all characters in the
		 * damaged frame are transmitted.
		 */
		case STATE_M_RX_ERROR:
			vMBMasterPortTimersT35Enable();
			break;

		/* In the idle state we wait for a new character. If a character
		 * is received the t1.5 and t3.5 timers are started and the
		 * receiver is in the state STATE_RX_RECEIVCE and disable early
		 * the timer of respond timeout .
		 */
		case STATE_M_RX_IDLE:
			/* In time of respond timeout,the receiver receive a frame.
			 * Disable timer of respond timeout and change the transmiter state to idle.
			 */
			vMBMasterPortTimersDisable();
			eSndState = STATE_M_TX_IDLE;

			usMasterRcvBufferPos = 0;
			ucMasterRTURcvBuf[usMasterRcvBufferPos++] = ucByte;
			eRcvState = STATE_M_RX_RCV;

			/* Enable t3.5 timers. */
			vMBMasterPortTimersT35Enable();
			break;

		/* We are currently receiving a frame. Reset the timer after
		 * every character received. If more than the maximum possible
		 * number of bytes in a modbus frame is received the frame is
		 * ignored.
		 */
		case STATE_M_RX_RCV:
			if(usMasterRcvBufferPos < MB_SER_PDU_SIZE_MAX) {
				ucMasterRTURcvBuf[usMasterRcvBufferPos++] = ucByte;
			} else {
				eRcvState = STATE_M_RX_ERROR;
			}

			vMBMasterPortTimersT35Enable();
			break;
	}

	return xTaskNeedSwitch;
}

BOOL
xMBMasterRTUTransmitFSM(void)
{
	BOOL            xNeedPoll = FALSE;

	assert_param(eRcvState == STATE_M_RX_IDLE);

	switch(eSndState) {
		/* We should not get a transmitter event if the transmitter is in
		 * idle state.  */
		case STATE_M_TX_IDLE:
			/* enable receiver/disable transmitter. */
			vMBMasterPortSerialEnable(TRUE, FALSE);
			break;

		case STATE_M_TX_XMIT:

			/* check if we are finished. */
			if(usMasterSndBufferCount != 0) {
				xMBMasterPortSerialPutByte((CHAR)*pucMasterSndBufferCur);
				pucMasterSndBufferCur++;  /* next byte in sendbuffer. */
				usMasterSndBufferCount--;
			} else {
				xFrameIsBroadcast = (ucMasterRTUSndBuf[MB_SER_PDU_ADDR_OFF] == MB_ADDRESS_BROADCAST) ? TRUE : FALSE;
				/* Disable transmitter. This prevents another transmit buffer
				 * empty interrupt. */
				vMBMasterPortSerialEnable(TRUE, FALSE);
				eSndState = STATE_M_TX_XFWR;

				/* If the frame is broadcast ,master will enable timer of convert delay,
				 * else master will enable timer of respond timeout. */
				if(xFrameIsBroadcast == TRUE) {
					vMBMasterPortTimersConvertDelayEnable();
				} else {
					vMBMasterPortTimersRespondTimeoutEnable();
				}
			}

			break;
	}

	return xNeedPoll;
}

BOOL
xMBMasterRTUTimerExpired(void)
{
	BOOL xNeedPoll = FALSE;

	switch(eRcvState) {
		/* Timer t35 expired. Startup phase is finished. */
		case STATE_M_RX_INIT:
			xNeedPoll = xMBMasterPortEventPost(EV_MASTER_READY);
			break;

		/* A frame was received and t35 expired. Notify the listener that
		 * a new frame was received. */
		case STATE_M_RX_RCV:
			xNeedPoll = xMBMasterPortEventPost(EV_MASTER_FRAME_RECEIVED);
			break;

		/* An error occured while receiving the frame. */
		case STATE_M_RX_ERROR:
			vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA);
			xNeedPoll = xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS);
			break;

		/* Function called in an illegal state. */
		default:
			assert_param(
			    (eRcvState == STATE_M_RX_INIT) || (eRcvState == STATE_M_RX_RCV) ||
			    (eRcvState == STATE_M_RX_ERROR) || (eRcvState == STATE_M_RX_IDLE));
			break;
	}

	eRcvState = STATE_M_RX_IDLE;

	switch(eSndState) {
		/* A frame was send finish and convert delay or respond timeout expired.
		 * If the frame is broadcast,The master will idle,and if the frame is not
		 * broadcast.Notify the listener process error.*/
		case STATE_M_TX_XFWR:
			if(xFrameIsBroadcast == FALSE) {
				vMBMasterSetErrorType(EV_ERROR_RESPOND_TIMEOUT);
				xNeedPoll = xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS);
			}

			break;

		/* Function called in an illegal state. */
		default:
			assert_param(
			    (eSndState == STATE_M_TX_XFWR) || (eSndState == STATE_M_TX_IDLE));
			break;
	}

	eSndState = STATE_M_TX_IDLE;

	vMBMasterPortTimersDisable();

	/* If timer mode is convert delay, the master event then turns EV_MASTER_EXECUTE status. */
	if(eMasterCurTimerMode == MB_TMODE_CONVERT_DELAY) {
		xNeedPoll = xMBMasterPortEventPost(EV_MASTER_EXECUTE);
	}

	return xNeedPoll;
}

/* Get Modbus Master send RTU's buffer address pointer.*/
void vMBMasterGetRTUSndBuf(UCHAR** pucFrame)
{
	*pucFrame = (UCHAR*) ucMasterRTUSndBuf;
}

/* Get Modbus Master send PDU's buffer address pointer.*/
void vMBMasterGetPDUSndBuf(UCHAR** pucFrame)
{
	*pucFrame = (UCHAR*) &ucMasterRTUSndBuf[MB_SER_PDU_PDU_OFF];
}

/* Set Modbus Master send PDU's buffer length.*/
void vMBMasterSetPDUSndLength(USHORT SendPDULength)
{
	usMasterSendPDULength = SendPDULength;
}

/* Get Modbus Master send PDU's buffer length.*/
USHORT usMBMasterGetPDUSndLength(void)
{
	return usMasterSendPDULength;
}

/* Set Modbus Master current timer mode.*/
void vMBMasterSetCurTimerMode(eMBMasterTimerMode eMBTimerMode)
{
	eMasterCurTimerMode = eMBTimerMode;
}

/* The master request is broadcast? */
BOOL xMBMasterRequestIsBroadcast(void)
{
	return xFrameIsBroadcast;
}
#endif

